<?php
/**
 * OpenID 2.0 Authentication for PHP
 *
 * PHP version 5.1.0
 *
 * LICENSE:
 * 
 * Copyright (c) 2006-2007 Pádraic Brady <padraic.brady@yahoo.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the 
 *      documentation and/or other materials provided with the distribution.
 *    * The name of the author may not be used to endorse or promote products 
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category  Auth
 * @package   OpenID
 * @author    Pádraic Brady <padraic.brady@yaho.com>
 * @copyright 2006-2007 Pádraic Brady <padraic.brady@yahoo.com>
 * @license   http://framework.zend.com/license/new-bsd     New BSD License
 * @link      http://pearopenid.googlecode.com
 */

require_once 'OpenID/Exception.php';

/**
 * Container for translating data in the {Key}:{Value} format delimited by newlines.
 * This class doesn't appear to be needed outside of OpenID but can moved to a
 * more central location if ever required.
 *
 * @category   Auth
 * @package    OpenID
 */
/**
 * OpenID_KVContainer 
 * 
 * @uses      Iterator
 * @category  Auth
 * @package   OpenID
 * @author    Pádraic Brady <padraic.brady@yaho.com>
 * @copyright 2006-2007 Pádraic Brady <padraic.brady@yahoo.com>
 * @license   http://framework.zend.com/license/new-bsd     New BSD License
 * @link      http://pearopenid.googlecode.com
 */
class OpenID_KVContainer implements Iterator
{

    /**
     * A simple array that holds the KV key=>value pairs
     *
     * @var array
     */
    protected $data = array();

    /**
     * Iterator validity for self::$data
     *
     * @var bool
     */
    protected $valid = true;

    /**
     * Prefix attached to all keys in form {prefix}.{key}
     *
     * @var string
     */
    protected $keyPrefix = null;

    /**
     * Constructor. Creates a container for parsing or generating strings which
     * implements a newline delimited Key:Value format.
     *
     * @param mixed  $data      string or array of kv pairs
     * @param string $keyPrefix Key prefix
     *
     * @return void
     */
    public function __construct($data = null, $keyPrefix = null)
    {
        if ($keyPrefix !== null) {
            $this->setKeyPrefix($keyPrefix);
        }
        if ($data !== null) {
            if (is_array($data)) {
                $this->fromArray($data);
            } else {
                $this->fromString($data);
            }
        }
    }

    /**
     * Constructs a KVContainer from a string input
     *
     * @param string $data String of kv pairs, not in array form
     *
     * @throws OpenID_Exception
     * @return void
     */
    public function fromString($data)
    {
        if (empty($data)) {
            throw new OpenID_Exception(
                'Invalid data; the data passed does not appear to contain anything'
            );
        }

        $kv = $this->parseString($data);
        if (!$kv) {
            $message  = 'Invalid data. The data passed does not appear to be in a ';
            $message .= 'valid Key:Value format where keys and values are separated';
            $message .= ' by a colon, and each pair is delimited by a newline';
            throw new OpenID_Exception($message);
        }
    }

    /**
     * Magically converts KVContainer into it's KV string representation.
     *
     * @return string
     */
    public function __toString()
    {
        $string = '';
        foreach ($this->data as $key => $value) {
            $string .= $key . ':' . (string) $value . PHP_EOL;
        }
        return $string;
    }

    /**
     * Proxies to __toString()
     *
     * @return string
     */
    public function toString()
    {
        return $this->__toString();
    }

    /**
     * Constructs the container using an array of values.
     *
     * @param array $data Array of KV pairs
     *
     * @throws OpenID_Exception
     * @return void
     */
    public function fromArray(array $data)
    {
        if (empty($data)) {
            throw new OpenID_Exception(
                'Invalid data; the data passed does not appear to contain anything'
            );
        }
        $kv = $this->parseArray($data);
        if (!$kv) {
            $message  = 'Invalid data; the data passed does not appear to be in a ';
            $message .= 'valid single-dimensional array';
            throw new OpenID_Exception($message);
        }
        $this->data = $kv;
    }

    /**
     * Extracts the array of key value pairs.
     *
     * @return array
     */
    public function toArray()
    {
        if (isset($this->data)) {
            return $this->data;
        }
        return null;
    }

    /**
     * Sets a prefix that is attached to all keys in the data array
     * with a period delimiter. e.g. the openid prefix creates keys
     * like "openid.ns". Prefixes are not required when referencing
     * values.
     *
     * @param string $prefix Key Prefix
     *
     * @return void
     */
    public function setKeyPrefix($prefix)
    {
        $this->keyPrefix = rtrim((string) $prefix, '.') . '.';
    }

    /**
     * Boolean return on whether next KV element is valid
     *
     * @return bool
     */
    public function valid()
    {
        return $this->valid;
    }

    /**
     * Returns current array element
     *
     * @return string
     */
    public function current()
    {
        return current($this->data);
    }

    /**
     * Moves array pointer forward by 1 and set Validity based on whether
     * this operation is successful
     *
     * @return bool
     */
    public function next()
    {
        $this->valid = (false !== next($this->data));
    }

    /**
     * Returns array pointer to the first element
     *
     * @return bool
     */
    public function rewind()
    {
        $this->valid = (false !== reset($this->data));
    }

    /**
     * Returns the key of the current array element
     *
     * @return bool
     */
    public function key()
    {
        return $this->keyPrefix . key($this->data);
    }

    /**
     * Sets KV values
     *
     * @param string $key   key
     * @param string $value value
     *
     * @return void
     */
    public function __set($key, $value)
    {
        if (strrpos($key, '.') === false && isset($this->keyPrefix)) {
            $key = $this->keyPrefix . $key;
        }
        $this->data[$key] = (string) $value;
    }

    /**
     * Gets KV values
     *
     * @param string $key Key to get
     *
     * @return string|null
     */
    public function __get($key)
    {
        if (strrpos($key, '.') === false && isset($this->keyPrefix)) {
            $key = $this->keyPrefix . $key;
        }
        if (isset($this->data[$key])) {
            return $this->data[$key];
        }
        return null;
    }

    /**
     * Overloads isset() check for KV values
     *
     * @param string $key Key
     *
     * @return bool
     */
    public function __isset($key)
    {
        if (strrpos($key, '.') === false && isset($this->keyPrefix)) {
            $key = $this->keyPrefix . $key;
        }
        return isset($this->data[$key]);
    }

    /**
     * Overloads unset() for KV values
     *
     * @param string $key Key
     *
     * @return void
     */
    public function __unset($key)
    {
        if (strrpos($key, '.') === false && isset($this->keyPrefix)) {
            $key = $this->keyPrefix . $key;
        }
        if ($this->__isset($key)) {
            unset($this->data[$key]);
        }
    }

    /**
     * Reset the KVContainer to a clean state
     *
     * @return void
     */
    public function reset()
    {
        $this->data = array();
        $this->rewind();
    }

    /**
     * Parses the string KV formed data into an array
     *
     * @param string $data KV Pair string
     *
     * @return bool true on success, false on failure
     */
    protected function parseString($data)
    {
        $kv    = array();
        $pairs = explode("\n", $data);

        if (count($pairs) == 0) {
            return false;
        }

        foreach ($pairs as $pair) {
            $pair = trim($pair);
            if (empty($pair)) {
                continue;
            }
            $pairSplit = explode(':', $pair, 2);
            if (!is_array($pairSplit) || count($pairSplit) !== 2) {
                return false;
            }
            $this->__set($pairSplit[0], $pairSplit[1]);
        }
        return true;
    }

    /**
     * Parses an array of values into KV formed data casting non-string data to
     * String
     *
     * @param array $data Array of KV pairs
     *
     * @return bool true on success, false on failure
     */
    protected function parseArray($data)
    {
        $kv = array();
        if (count($data) == 0) {
            return false;
        }
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                return false;
            }
            $this->__set($key, (string) $value);
        }
        return true;
    }

}
?>
